package http

import (
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"gitee.com/shoyer/logger"
	"gitee.com/shoyer/util"
	"gitee.com/shoyer/util/moment"
	"github.com/spf13/cast"
	"golang.org/x/net/proxy"
)

func request(method, ur string, ops ...*Options) (res []byte, resp *http.Response, err error) {
	body := []byte{}

	if len(ops) > 1 {
		err = errors.New("only one option can be accepted, pls check")
		return
	}

	if len(ops) > 0 && len(ops[0].Params) > 0 {
		ur = fmt.Sprintf("%s?%s", ur, ops[0].Params.Encode())
	}

	if len(ops) > 0 {
		body = ops[0].Body
	}

	var porxyURL *url.URL
	var dialer proxy.Dialer
	if len(ops) > 0 && ops[0].Proxy != "" {
		if strings.Contains(ops[0].Proxy, "http") {
			porxyURL, err = url.Parse(ops[0].Proxy)
		} else {
			dialer, err = proxy.SOCKS5("tcp", ops[0].Proxy, nil, proxy.Direct)
		}
	}

	if err != nil {
		return
	}

	// id := util.GetRandomStringNumber(16)
	opt := util.ObjectToJSONString(ops[0])
	// if isDebug(ops...) {
	// 	logger.Infof("%s Http %s Request: %s", logger.Magenta(id), method, logger.HiCyan(ur))
	// 	logger.Infof("%s Http %s Options: %s", logger.Magenta(id), method, logger.HiCyan(opt))

	// 	if len(body) > 0 {
	// 		logger.Infof("%s Http %s Body: %s", logger.Magenta(id), method, logger.HiCyan(cast.ToString(body)))
	// 	}
	// }

	u, err := url.Parse(ur)
	if err != nil {
		return
	}

	req, err := http.NewRequest(method, ur, bytes.NewBuffer(body))
	if err != nil {
		return
	}
	req.Host = u.Host

	transport := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
			ServerName:         req.Host, // for fix : remote error: tls: internal error
		},
		Proxy:             http.ProxyURL(porxyURL),
		DisableKeepAlives: true, // refer: https://www.cnblogs.com/cobbliu/p/4517598.html,   https://www.jianshu.com/p/14edebc91c1b
	}

	if dialer != nil {
		transport.Dial = dialer.Dial
	}

	client := &http.Client{Transport: transport}

	// 添加Header
	if len(ops) > 0 {
		op := ops[0]
		for k, v := range op.Headers {
			req.Header.Add(k, v)
		}

		if op.Timeout > 0 {
			client.Timeout = op.Timeout
		}
	}

	// 发起请求
	start := moment.NewMoment()
	req.Close = true // https://tgyf007.github.io/2019/04/01/golang_http_get_unpected/,  https://blog.csdn.net/weixin_34221773/article/details/91544432
	resp, err = client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	res, err = io.ReadAll(resp.Body)

	if len(ops) > 0 {
		ops[0].SetRawRes(res)
		ops[0].SetResponse(resp)
	}

	end := moment.NewMoment()

	if isDebug(ops...) {
		// logger.Infof("%s Http %s Response(%s)(%s): %s", logger.Magenta(id), method, logger.HiGreen(cast.ToString(resp.StatusCode)), logger.HiYellow(moment.Duration(end.Sub(start.Time)).FormatEn(true)), logger.HiCyan(cast.ToString(res)))
		logger.Infof("Http %s %s (%s)(%s)(%s) \noptions: %s \nbody: %s \nresponse: %s", logger.HiCyan(method), logger.HiMagenta(ur), logger.HiGreen(cast.ToString(resp.StatusCode)),
			logger.HiYellow(moment.Duration(end.Sub(start.Time)).FormatEn(true)),
			logger.HiCyan(util.FormatSize(len(res))),
			logger.White(opt), logger.HiYellow(cast.ToString(body)), logger.HiCyan(cast.ToString(res)))
	}

	return
}

// 保持连接版本
var (
	clients = &sync.Map{}
)

func getClient(proxy *url.URL, dialer proxy.Dialer) *http.Client {
	key := "default"
	if proxy != nil {
		key = proxy.String()
	}

	if p, ok := clients.Load(key); ok {
		return p.(*http.Client)
	}

	transport := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
		Proxy:                 http.ProxyURL(proxy),
		ForceAttemptHTTP2:     true,
		MaxIdleConnsPerHost:   100,
		MaxIdleConns:          0, // no limit
		MaxConnsPerHost:       0, // no limit
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}

	if dialer != nil {
		transport.Dial = dialer.Dial
	}

	client := &http.Client{Transport: transport}
	clients.Store(key, client)
	return client
}

// 无复用版本
func getClientV2(proxy *url.URL, dialer proxy.Dialer) *http.Client {
	transport := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
		Proxy:                 http.ProxyURL(proxy),
		ForceAttemptHTTP2:     true,
		MaxIdleConnsPerHost:   100,
		MaxIdleConns:          0, // no limit
		MaxConnsPerHost:       0, // no limit
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}

	if dialer != nil {
		transport.Dial = dialer.Dial
	}

	client := &http.Client{Transport: transport}
	return client
}

func requestv2(method, ur string, ops ...*Options) (res []byte, resp *http.Response, err error) {
	body := []byte{}

	if len(ops) > 1 {
		err = errors.New("only one option can be accepted, pls check")
		return
	}

	if len(ops) > 0 && len(ops[0].Params) > 0 {
		ur = fmt.Sprintf("%s?%s", ur, ops[0].Params.Encode())
	}

	if len(ops) > 0 {
		body = ops[0].Body
	}

	var porxyURL *url.URL
	var dialer proxy.Dialer
	if len(ops) > 0 && ops[0].Proxy != "" {
		if strings.Contains(ops[0].Proxy, "http") {
			porxyURL, err = url.Parse(ops[0].Proxy)
		} else {
			dialer, err = proxy.SOCKS5("tcp", ops[0].Proxy, nil, proxy.Direct)
		}
	}

	if err != nil {
		return
	}

	opt := util.ObjectToJSONString(ops[0])
	u, err := url.Parse(ur)
	if err != nil {
		return
	}

	req, err := http.NewRequest(method, ur, bytes.NewBuffer(body))
	if err != nil {
		return
	}
	req.Host = u.Host

	client := getClientV2(porxyURL, dialer)

	// 添加Header
	if len(ops) > 0 {
		op := ops[0]
		for k, v := range op.Headers {
			req.Header.Add(k, v)
		}

		if op.Timeout > 0 {
			client.Timeout = op.Timeout
		} else {
			client.Timeout = time.Second * 5
		}
	}

	// 发起请求
	start := moment.NewMoment()
	resp, err = client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	res, err = io.ReadAll(resp.Body)

	if len(ops) > 0 {
		ops[0].SetRawRes(res)
		ops[0].SetResponse(resp)
	}

	end := moment.NewMoment()

	if isDebug(ops...) {
		// logger.Infof("%s Http %s Response(%s)(%s): %s", logger.Magenta(id), method, logger.HiGreen(cast.ToString(resp.StatusCode)), logger.HiYellow(moment.Duration(end.Sub(start.Time)).FormatEn(true)), logger.HiCyan(cast.ToString(res)))
		logger.Infof("Http %s %s (%s)(%s)(%s) \noptions: %s \nbody: %s \nresponse: %s", logger.HiCyan(method), logger.HiMagenta(ur), logger.HiGreen(cast.ToString(resp.StatusCode)),
			logger.HiYellow(moment.Duration(end.Sub(start.Time)).FormatEn(true)),
			logger.HiCyan(util.FormatSize(len(res))),
			logger.White(opt), logger.HiYellow(cast.ToString(body)), logger.HiCyan(cast.ToString(res)))
	}

	return
}
